//
// itertool - a polynormial and type-safe iterator framework in C++.
//
// Copyright (c) 2021-2025 Feng Cong
//
// Distributed under the MIT Software License
// See accompanying file LICENSE.txt or copy at
// https://opensource.org/licenses/MIT

#pragma once

#include <itertool/itertool_fwd.h>

#include <optional>
#include <type_traits>

#if !defined(__cpp_lib_optional)
#error "Filter requires std::optional<T> class template to work."
#endif

namespace itertool {

namespace detail {

template <typename T, typename Function, typename = void>
struct FilterIterableRequires : std::false_type {};

template <typename T, typename Function>
struct FilterIterableRequires<
    T, Function,
    std::void_t<std::enable_if_t<std::is_copy_constructible_v<Function>>,
                decltype(static_cast<bool>(std::declval<Function>()(
                    std::declval<const T&>())))>> : std::true_type {};

template <typename T, typename Function>
using FilterIterableBase =
    detail::IterableBase<T /*New*/, T /*Old*/, Function /*Extra*/>;

template <typename T, typename Function>
class FilterIterable : public FilterIterableBase<T, Function> {
  static_assert(
      FilterIterableRequires<T, Function>::value,
      "BasicFilter requires Function's signature to be compatible with "
      "bool(const T&) and it should be copy-concstructible");

 public:
  using ItemType = T;

  FilterIterable(Iterator<T> it, Function func, bool find_true)
      : FilterIterableBase<T, Function>(it, func), kFindTrue(find_true) {
    FindNext();
  }

  bool HasNext() override { return item_.has_value(); }

  ItemType GetNow() override {
    assert(HasNext());
    return std::forward<ItemType>(item_->value);
  }

  void Advance() override { FindNext(); }

 private:
  void FindNext() {
    auto& it = this->BaseGetIterator();
    auto& func = this->BaseGetExtra();

    while (it.HasNext()) {
      ItemType&& val = it.GetNow();
      it.Advance();
      const bool ok = func(const_cast<const ItemType&>(val));
      if (ok == kFindTrue) {
        item_.emplace(AnyValue<ItemType>(std::forward<ItemType>(val)));
        return;
      }
    }
    item_.reset();
  }

  const bool kFindTrue;
  std::optional<AnyValue<ItemType>> item_;
};

}  // namespace detail

template <typename T, typename Function>
Iterator<T> MakeFilter(Iterator<T> it, Function func, bool find_true) {
  return MakeIterator<detail::FilterIterable<T, Function>>(it, func, find_true);
}

template <typename T, typename Function>
Iterator<T> Filter(Iterator<T> it, Function func) {
  return MakeFilter(it, func, true);
}

template <typename T, typename Function>
Iterator<T> FilterFalse(Iterator<T> it, Function func) {
  return MakeFilter(it, func, false);
}

}  // namespace itertool
